Skip to content

feat(api): track unread thread count - #978

Merged
AchoArnold merged 5 commits into
mainfrom
feat/add-unread-count
Sep 1, 2026
Merged

feat(api): track unread thread count#978
AchoArnold merged 5 commits into
mainfrom
feat/add-unread-count

Conversation

@AchoArnold

Copy link
Copy Markdown
Member

Summary

  • replace message thread is_read state with unread_count
  • increment unread counts for inbound messages and preserve them for outbound activity
  • update API docs, web thread UI/state, unit tests, and integration tests

Breaking change

Message thread responses and update payloads now use unread_count instead of is_read.

Tests

  • cd api && go test ./...
  • cd tests && go test -run '^$' ./...
  • staged web ESLint, Stylelint, and Prettier checks

Full Docker integration execution was not run because Docker is unavailable in the local environment.

Replace the boolean read state with an unread message counter so repeated
inbound activity remains visible until the thread is explicitly cleared.

BREAKING CHANGE: Message thread responses and updates use unread_count
instead of is_read.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8efee0ef-a8be-4d9c-b4ec-33dcbf0dfdfd
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics -2 duplication

Metric Results
Duplication -2

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@greptile-apps

greptile-apps Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the message-thread read boolean with a persisted unread counter and updates inbound-event handling, the API contract, dashboard state, documentation, and tests.

  • Increments unread counts for received messages and missed calls while preserving counts during outbound activity.
  • Adds API and dashboard support for resetting and displaying numeric unread counts.
  • Updates generated schemas and read-receipt tests for the new representation.

Confidence Score: 3/5

This PR should not merge until delayed inbound events can no longer restore stale unread state and the missed-call preview contract is made consistent.

Removing the read watermark permits an older queued event to mark an already-read thread unread again, and the changed missed-call literal causes existing unit and integration assertions to fail.

Files Needing Attention: api/pkg/repositories/gorm_message_thread_repository.go, api/pkg/listeners/message_thread_listener.go

Important Files Changed

Filename Overview
api/pkg/repositories/gorm_message_thread_repository.go Implements atomic unread increments and resets, but removes the event-time watermark needed to order delayed inbound events against user read actions.
api/pkg/services/message_thread_service.go Replaces read-state service parameters with unread counts and initializes new threads according to inbound direction.
api/pkg/listeners/message_thread_listener.go Marks inbound events for unread increments but introduces a missed-call preview literal that conflicts with existing tests.
web/app/pages/threads/[id]/index.vue Resolves inbound websocket events to their message thread before resetting the selected thread's unread count.
web/app/stores/threads.ts Updates dashboard thread state to reset unread_count and preserve contact enrichment in returned thread records.
web/app/components/MessageThread.vue Displays unread counts as capped badges and applies unread emphasis based on a positive count.
tests/read_receipts_test.go Covers unread counting and reset behavior, but retains the old missed-call preview casing.

Sequence Diagram

sequenceDiagram
  participant P as Phone/API
  participant Q as Event queue
  participant T as Thread listener
  participant DB as Thread repository
  participant W as Web dashboard
  P->>Q: Inbound message event
  Q-->>T: Asynchronous delivery
  T->>DB: "unread_count = unread_count + 1"
  W->>DB: "PUT unread_count = 0"
  DB-->>W: Updated thread
Loading

Reviews (1): Last reviewed commit: "feat(api)!: track unread thread count" | Re-trigger Greptile

Comment thread api/pkg/repositories/gorm_message_thread_repository.go
Comment thread api/pkg/listeners/message_thread_listener.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates httpSMS message threads to track an integer unread_count instead of a boolean is_read, aligning backend persistence, API contracts/docs, web UI state, and tests to support incrementing unread counts on inbound activity and clearing them via thread updates.

Changes:

  • Replace thread read state (is_read) with unread_count across API entities, request payloads, repository updates, and generated Swagger docs.
  • Update web thread stores/pages/components to display unread counts and clear them via unread_count: 0 updates.
  • Update unit + integration tests and test documentation to validate unread-count behavior.

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
web/shared/types/api.ts Updates generated TS API types to remove is_read and add unread_count.
web/app/stores/threads.ts Renames “mark read” behavior to clearing unread_count and updates update payload accordingly.
web/app/stores/messages.ts Adds getMessage() helper used by thread page websocket handling.
web/app/pages/threads/[id]/index.vue Updates thread page to clear unread count and attempts to handle inbound websocket events using message_id.
web/app/components/MessageThread.vue Updates thread list UI to show unread badge and bold styling based on unread_count.
tests/README.md Updates E2E checklist item wording to “unread count”.
tests/read_receipts_test.go Renames/updates integration test expectations from is_read to unread_count.
api/pkg/validators/message_thread_handler_validator.go Requires update payload contain is_archived or unread_count.
api/pkg/validators/message_thread_handler_validator_test.go Updates validator test to use UnreadCount field.
api/pkg/services/message_thread_service.go Replaces read-state updates with UnreadCount updates; initializes new threads with unread count.
api/pkg/services/message_thread_service_test.go Updates service tests to validate unread-count behavior (no increment on outbound, etc.).
api/pkg/requests/message_thread_update_request.go Swaps request payload field from is_read to unread_count.
api/pkg/requests/message_thread_update_request_test.go Updates request→params mapping test for UnreadCount.
api/pkg/repositories/message_thread_repository.go Replaces status update struct fields to carry UnreadCount instead of read timestamps.
api/pkg/repositories/gorm_message_thread_repository.go Implements unread-count increment/update logic in GORM updates and thread store upsert.
api/pkg/repositories/gorm_message_thread_repository_test.go Updates repository tests for unread-count update expressions and conflict behavior.
api/pkg/listeners/message_thread_listener.go Adjusts missed-call thread preview content (and still marks thread unread via activity).
api/pkg/handlers/message_thread_handler_test.go Updates handler test request payload to {"unread_count":0}.
api/pkg/entities/message_thread.go Replaces IsRead/LastReadAt with persisted UnreadCount.
api/pkg/entities/message_thread_test.go Updates struct-tag/default test for UnreadCount.
api/docs/swagger.yaml Updates OpenAPI definitions to remove is_read and add unread_count.
api/docs/swagger.json Updates generated Swagger JSON accordingly.
api/docs/docs.go Updates generated swagger docs template accordingly.
Files not reviewed (1)
  • api/docs/docs.go: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread api/pkg/repositories/gorm_message_thread_repository.go Outdated
Comment thread api/pkg/listeners/message_thread_listener.go
Comment thread web/app/pages/threads/[id]/index.vue Outdated
AchoArnold and others added 2 commits August 31, 2026 22:39
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • api/docs/docs.go: Generated file
Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

api/pkg/repositories/gorm_message_thread_repository_test.go:195

  • Duplicate assert.Equal in TestMessageThreadStatusUpdatesArchiveOnly is redundant and makes the test noisier than necessary.
	})
	assert.Equal(t, map[string]any{"is_archived": true}, updates)
	assert.Equal(t, map[string]any{"is_archived": true}, updates)
	assert.NotContains(t, updates, "unread_count")

api/pkg/repositories/gorm_message_thread_repository.go:51

  • Unread-count increments in messageThreadActivityUpdates are not idempotent: reprocessing the same inbound event can over-increment unread_count since it always does unread_count + 1 and no longer uses EventTimestamp as a watermark. Consider guarding the increment so re-applying the same MessageID does not change the count.
		updates["is_archived"] = false
	}
	if params.MarkAsUnread {
		updates["unread_count"] = gorm.Expr("unread_count + ?", 1)
	}
	return updates

Comment thread api/pkg/entities/message_thread.go Outdated
Comment on lines 11 to 16
@@ -12,8 +12,7 @@ type MessageThread struct {
Owner string `json:"owner" example:"+18005550199"`
Contact string `json:"contact" example:"+18005550100"`
IsArchived bool `json:"is_archived" example:"false"`
IsRead bool `json:"is_read" gorm:"not null;default:true" example:"true"`
LastReadAt time.Time `json:"-" gorm:"not null;default:CURRENT_TIMESTAMP"`
UnreadCount uint `json:"unread_count" gorm:"not null;default:0" example:"0"`
UserID UserID `json:"user_id" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"`

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 283621e. Added the composite unique index on (user_id, owner, contact), with explicit column priorities matching the ON CONFLICT target, plus regression coverage for the GORM tags.

Create the composite unique index required by the message-thread upsert so
fresh databases can execute its ON CONFLICT target.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8efee0ef-a8be-4d9c-b4ec-33dcbf0dfdfd
@AchoArnold
AchoArnold merged commit c2d357c into main Sep 1, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants